package cn.itcast.nems.studentrecord.service.impl;

import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.ic.common.exception.v2.bean.BizStatusEnum;
import cn.itcast.nems.common.bean.LocalDateTimeRange;
import cn.itcast.nems.studentrecord.dao.entity.StudentRecordTransaction;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordChangeCheckDTO;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordChangeCheckOriginDTO;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordChangeCheckResult;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordTransactionChangeCheckDTO;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordStateEnum;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 检查学籍变更是否允许
 *
 * @author liyong
 * create: 2023-11-29 10:21:30
 **/
class StudentRecordChangeChecker {
    final StudentRecordChangeCheckOriginDTO studentRecordOrigin;
    // 学籍时间范围 key: 学籍ID， value: 出入班时间范围
    final Map<String, LocalDateTimeRange> studentRecordDateRangeMap;
    // 学籍是否可用变更
    boolean canChange;
    // 如果不能变更，为不能变更的原因
    String reason = "";
    // 是否忽略对学籍锁的检查
    boolean ignoredLock = false;

    /**
     * 检查学籍是否可以变更
     *
     * @param studentRecordOrigin       要变更的源学籍
     * @param studentRecordTransactions 学员学籍流转记录
     */
    StudentRecordChangeChecker(StudentRecordChangeCheckOriginDTO studentRecordOrigin,
                               List<StudentRecordTransactionChangeCheckDTO> studentRecordTransactions) {
        this.studentRecordOrigin = studentRecordOrigin;
        this.studentRecordDateRangeMap = this.transactionToStudentRecordDateRange(studentRecordTransactions);
    }

    /**
     * 检查学籍变更是否可以
     *
     * @param dto 学籍变化信息
     * @return 检查结果 canChange=false表示学籍变更不允许，reason为不能变更的原因
     */
    StudentRecordChangeCheckResult canChange(StudentRecordChangeCheckDTO dto) {
        this.canChange = true;
        this.checkOriginStudentRecord();
        this.checkOriginTarget(dto.getStudentId());
        this.checkLockFlag();
        this.checkDateOverlap(dto);
        return new StudentRecordChangeCheckResult(this.isCanChange(), this.getReason());
    }

    /**
     * 检查原学籍是否满足出班要求
     */
    void checkOriginStudentRecord() {
        if (!StudentRecordStateEnum.VALID.name().equals(this.studentRecordOrigin.getState())) {
            this.cannotChange("原班级学籍尚未生效");
        }
        if (this.studentRecordOrigin.getSettlementDate() != null) {
            this.cannotChange("原班级已经出班");
        }
    }

    /**
     * 检查原班级和目标班级
     *
     * @param targetClazzId 目标班级
     */
    void checkOriginTarget(Integer targetClazzId) {
        final int originClazzId = this.studentRecordOrigin.getClazzId();
        if (originClazzId == targetClazzId) {
            this.cannotChange("原班级和目标班为同一个班");
        }
    }

    /**
     * 检查学籍锁状态
     */
    void checkLockFlag() {
        final Boolean lockFlag = studentRecordOrigin.getLockFlag();
        if (!ignoredLock && lockFlag != null && lockFlag) {
            this.cannotChange("学籍已锁定");
        }
    }

    void cannotChange(String reason) {
        this.canChange = false;
        this.reason += reason + " ";
    }

    /**
     * 学籍是否可以变更
     *
     * @return 是否可以变更
     */
    boolean isCanChange() {
        return canChange;
    }

    /**
     * 如果不能变更，为不能变更的原因
     *
     * @return 不能变更的原因
     */
    String getReason() {
        return reason;
    }

    void setIgnoredLock(boolean ignoredLock) {
        this.ignoredLock = ignoredLock;
    }

    /**
     * 判断学籍的上课日期是否有重叠
     *
     * @param dto 学籍变化信息
     */
    void checkDateOverlap(StudentRecordChangeCheckDTO dto) {
        if (!this.studentRecordDateRangeMap.isEmpty()) {
            this.studentRecordDateRangeMap.forEach((srId, r) -> {
                LocalDateTimeRange range = r;
                if (srId.equals(this.studentRecordOrigin.getId())) {
                    range = new LocalDateTimeRange(r.getStart(), dto.getOutDate().atStartOfDay());
                }
                if (range.getStart() == null) {
                    BizExceptionProducer.throwServerError(BizStatusEnum.INTERNAL_SERVER_ERROR.name(), "学籍'%s'没有找到入班流转记录", srId);
                }
                if (range.getEnd() == null) {
                    BizExceptionProducer.throwServerError(BizStatusEnum.INTERNAL_SERVER_ERROR.name(), "学籍'%s'没有找到出班流转记录", srId);
                }
                if (range.contains(dto.getInDate().atStartOfDay())) {
                    this.cannotChange("入班时间与学籍'" + srId + "'重叠");
                }
            });
        }
    }

    // 将流转记录转成学籍与日期范围的映射
    private Map<String, LocalDateTimeRange> transactionToStudentRecordDateRange(List<StudentRecordTransactionChangeCheckDTO> studentRecordTransactions) {
        if (studentRecordTransactions != null && !studentRecordTransactions.isEmpty()) {
            Map<String, LocalDateTimeRange> srRange = new HashMap<>();
            for (StudentRecordTransactionChangeCheckDTO t : studentRecordTransactions) {
                final LocalDateTimeRange range = srRange.computeIfAbsent(t.getStudentRecordId(), srId -> new LocalDateTimeRange());
                if (StudentRecordTransaction.TYPE_IN.equals(t.getType())) {
                    range.setStart(t.getBizDate());
                } else {
                    range.setEnd(t.getBizDate());
                }
            }
            return srRange;
        }
        return Collections.emptyMap();
    }

}
